using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;

namespace HakoJS.SourceGenerator;

public partial class JSBindingGenerator
{
    #region Identifier Helpers

    /// <summary>
    ///     Escapes an identifier if it's a C# reserved keyword
    /// </summary>
    private static string EscapeIdentifierIfNeeded(string identifier)
    {
        if (string.IsNullOrEmpty(identifier))
            return identifier;
        return SyntaxFacts.IsReservedKeyword(SyntaxFacts.GetKeywordKind(identifier)) ? $"@{identifier}" : identifier;
    }

    #endregion

    #region Object Binding Generation

    private static string GenerateObjectBinding(ObjectModel model)
    {
        var sb = new StringBuilder();

        sb.AppendLine("// <auto-generated/>");
        sb.AppendLine("#nullable enable");
        sb.AppendLine();
        sb.AppendLine("using System;");
        sb.AppendLine("using System.Linq;");
        sb.AppendLine("using System.Threading.Tasks;");
        sb.AppendLine("using HakoJS.VM;");
        sb.AppendLine("using HakoJS.Extensions;");
        sb.AppendLine("using HakoJS.SourceGeneration;");
        sb.AppendLine();
        var isNested = model.TypeSymbol?.ContainingType != null;
        if (!isNested && !string.IsNullOrEmpty(model.SourceNamespace))
            sb.AppendLine($"namespace {model.SourceNamespace};");

        sb.AppendLine();

        var abstractModifier = model.IsAbstract() ? "abstract " : "";

        sb.AppendLine(
            $"{abstractModifier}partial record {model.TypeName} : global::HakoJS.SourceGeneration.IJSMarshalable<{model.TypeName}>, global::HakoJS.SourceGeneration.IDefinitelyTyped<{model.TypeName}>");
        sb.AppendLine("{");

        GenerateObjectToJSValue(sb, model);
        sb.AppendLine();
        GenerateObjectFromJSValue(sb, model);

        sb.AppendLine();

        var newModifier = model.HasJSObjectBase() ? "new " : "";
        sb.AppendLine($"    public static {newModifier}string TypeDefinition");
        sb.AppendLine("    {");
        sb.AppendLine("        get");
        sb.AppendLine("        {");
        var escapedTypeScript = model.TypeScriptDefinition.Replace("\"", "\"\"");
        sb.AppendLine("            return @\"");
        sb.Append(escapedTypeScript);
        sb.AppendLine("\";");
        sb.AppendLine("        }");
        sb.AppendLine("    }");

        sb.AppendLine("}");

        return sb.ToString();
    }

    private static bool NeedsIntermediateDisposal(TypeInfo typeInfo)
    {
        if (typeInfo.IsArray || typeInfo.IsGenericCollection || typeInfo.IsGenericDictionary)
            return true;

        return typeInfo.SpecialType switch
        {
            SpecialType.System_Boolean => false,
            _ => true
        };
    }

    private static void GenerateObjectMethod(StringBuilder sb, ObjectModel model, MethodModel method, string indent)
    {
        var funcType = method.IsAsync ? "NewFunctionAsync" : "NewFunction";
        var asyncPrefix = method.IsAsync ? "async " : "";

        sb.AppendLine(
            $"{indent}using var {ToCamelCase(method.Name)}Func = realm.{funcType}(\"{method.JsName}\", {asyncPrefix}(ctx, thisArg, args) =>");
        sb.AppendLine($"{indent}{{");

        var requiredParams = method.Parameters.Count(p => !p.IsOptional);
        if (requiredParams > 0)
        {
            sb.AppendLine($"{indent}    if (args.Length < {requiredParams})");
            sb.AppendLine(
                $"{indent}        return ctx.ThrowError(global::HakoJS.VM.JSErrorType.Type, \"{method.JsName}() requires at least {requiredParams} argument(s)\");");
            sb.AppendLine();
        }

        sb.AppendLine($"{indent}    try");
        sb.AppendLine($"{indent}    {{");

        for (var i = 0; i < method.Parameters.Count; i++)
        {
            var param = method.Parameters[i];
            var argName = $"args[{i}]";
            var varName = param.Name;
            var isAnyType = param.TypeInfo.SpecialType == SpecialType.System_Object;

            if (param.IsOptional)
            {
                sb.AppendLine(
                    $"{indent}        var {varName} = args.Length > {i} ? {GetUnmarshalWithDefault(param.TypeInfo, argName, param.DefaultValue)} : {param.DefaultValue ?? GetDefaultValueForType(param.TypeInfo)};");
            }
            else
            {
                sb.AppendLine($"{indent}        if ({argName}.IsNullOrUndefined())");
                sb.AppendLine(
                    $"{indent}            return ctx.ThrowError(global::HakoJS.VM.JSErrorType.Type, \"Parameter '{varName}' cannot be null or undefined\");");
                if (!isAnyType)
                {
                    sb.AppendLine($"{indent}        if (!{GetTypeCheck(param.TypeInfo, argName)})");
                    sb.AppendLine(
                        $"{indent}            return ctx.ThrowError(global::HakoJS.VM.JSErrorType.Type, \"Parameter '{varName}' must be {GetTypeName(param.TypeInfo)}\");");
                }

                sb.AppendLine($"{indent}        var {varName} = {GetStrictUnmarshalCode(param.TypeInfo, argName)};");
            }
        }

        var callArgs = string.Join(", ", method.Parameters.Select(p => p.Name));

        if (method.IsAsync)
        {
            if (!method.IsVoid)
            {
                sb.AppendLine($"{indent}        var result = await {method.Name}({callArgs});");
                sb.AppendLine($"{indent}        return {GetMarshalCode(method.ReturnType, "result", "ctx")};");
            }
            else
            {
                sb.AppendLine($"{indent}        await {method.Name}({callArgs});");
                sb.AppendLine($"{indent}        return ctx.Undefined();");
            }
        }
        else
        {
            if (!method.IsVoid)
            {
                sb.AppendLine($"{indent}        var result = {method.Name}({callArgs});");
                sb.AppendLine($"{indent}        return {GetMarshalCode(method.ReturnType, "result", "ctx")};");
            }
            else
            {
                sb.AppendLine($"{indent}        {method.Name}({callArgs});");
                sb.AppendLine($"{indent}        return ctx.Undefined();");
            }
        }

        sb.AppendLine($"{indent}    }}");
        sb.AppendLine($"{indent}    catch (global::System.Exception ex)");
        sb.AppendLine($"{indent}    {{");
        sb.AppendLine($"{indent}        return ctx.ThrowError(ex);");
        sb.AppendLine($"{indent}    }}");
        sb.AppendLine($"{indent}}});");
        sb.AppendLine($"{indent}obj.SetProperty(\"{method.JsName}\", {ToCamelCase(method.Name)}Func);");
    }

    private static void GenerateObjectToJSValue(StringBuilder sb, ObjectModel model)
    {
        string methodSignature;
        if (model.IsAbstract())
        {
            methodSignature = "public abstract global::HakoJS.VM.JSValue ToJSValue(global::HakoJS.VM.Realm realm);";
            sb.AppendLine($"    {methodSignature}");
            return;
        }

        if (model.HasJSObjectBase())
            methodSignature = "public override global::HakoJS.VM.JSValue ToJSValue(global::HakoJS.VM.Realm realm)";
        else
            methodSignature = "public global::HakoJS.VM.JSValue ToJSValue(global::HakoJS.VM.Realm realm)";

        sb.AppendLine($"    {methodSignature}");
        sb.AppendLine("    {");
        sb.AppendLine("        var obj = realm.NewObject();");
        sb.AppendLine("        try");
        sb.AppendLine("        {");

        sb.AppendLine($"            using var typeIdValue = realm.NewNumber({model.GetTypeId()});");
        sb.AppendLine(
            "            obj.SetPropertyWithDescriptor(\"_hako_id\", typeIdValue, writable: false, enumerable: false, configurable: false);");
        sb.AppendLine();

        foreach (var param in model.Parameters)
            if (param.IsDelegate && param.DelegateInfo != null)
            {
                GenerateDelegateToFunction(sb, param, "            ");
            }
            else
            {
                var marshalCode = GetMarshalCode(param.TypeInfo, param.Name, "realm");

                if (NeedsIntermediateDisposal(param.TypeInfo))
                {
                    var tempVarName = $"{param.Name}Value";

                    if (param.TypeInfo is { IsNullable: true, IsValueType: false })
                    {
                        sb.AppendLine($"            if ({param.Name} != null)");
                        sb.AppendLine("            {");
                        sb.AppendLine($"                using var {tempVarName} = {marshalCode};");
                        sb.AppendLine($"                obj.SetProperty(\"{param.JsName}\", {tempVarName});");
                        sb.AppendLine("            }");
                    }
                    else
                    {
                        sb.AppendLine($"            using var {tempVarName} = {marshalCode};");
                        sb.AppendLine($"            obj.SetProperty(\"{param.JsName}\", {tempVarName});");
                    }
                }
                else
                {
                    if (param.TypeInfo is { IsNullable: true, IsValueType: false })
                    {
                        sb.AppendLine($"            if ({param.Name} != null)");
                        sb.AppendLine($"                obj.SetProperty(\"{param.JsName}\", {marshalCode});");
                    }
                    else
                    {
                        sb.AppendLine($"            obj.SetProperty(\"{param.JsName}\", {marshalCode});");
                    }
                }
            }

        foreach (var prop in model.Properties)
        {
            var propValue = prop.IsStatic ? $"{model.TypeName}.{prop.Name}" : prop.Name;
            var marshalCode = GetMarshalCode(prop.TypeInfo, propValue, "realm");

            if (NeedsIntermediateDisposal(prop.TypeInfo))
            {
                var tempVarName = $"{prop.Name}PropValue";
                sb.AppendLine($"            using var {tempVarName} = {marshalCode};");
                sb.AppendLine($"            obj.SetProperty(\"{prop.JsName}\", {tempVarName});");
            }
            else
            {
                sb.AppendLine($"            obj.SetProperty(\"{prop.JsName}\", {marshalCode});");
            }
        }

        foreach (var method in model.Methods) GenerateObjectMethod(sb, model, method, "            ");

        if (model.ReadOnly) sb.AppendLine("            obj.Freeze(realm);");

        sb.AppendLine("            return obj;");
        sb.AppendLine("        }");
        sb.AppendLine("        catch");
        sb.AppendLine("        {");
        sb.AppendLine("            obj.Dispose();");
        sb.AppendLine("            throw;");
        sb.AppendLine("        }");
        sb.AppendLine("    }");
    }


    private static void GenerateDelegateToFunction(StringBuilder sb, RecordParameterModel param, string indent)
    {
        var delegateInfo = param.DelegateInfo!;
        var funcType = delegateInfo.IsAsync ? "NewFunctionAsync" : "NewFunction";

        if (param.TypeInfo is { IsNullable: true, IsValueType: false })
        {
            sb.AppendLine($"{indent}if ({param.Name} != null)");
            sb.AppendLine($"{indent}{{");
            indent += "    ";
        }

        var asyncPrefix = delegateInfo.IsAsync ? "async " : "";
        sb.AppendLine(
            $"{indent}using var {param.Name}Func = realm.{funcType}(\"{param.JsName}\", {asyncPrefix}(ctx, thisArg, args) =>");
        sb.AppendLine($"{indent}{{");

        var requiredParams = delegateInfo.Parameters.Count(p => !p.IsOptional);
        if (requiredParams > 0)
        {
            sb.AppendLine($"{indent}    if (args.Length < {requiredParams})");

            sb.AppendLine(
                $"{indent}        return ctx.ThrowError(global::HakoJS.VM.JSErrorType.Type, \"{param.JsName}() requires at least {requiredParams} argument(s)\");");
            sb.AppendLine();
        }

        sb.AppendLine($"{indent}    try");
        sb.AppendLine($"{indent}    {{");

        for (var i = 0; i < delegateInfo.Parameters.Count; i++)
        {
            var p = delegateInfo.Parameters[i];
            var argName = $"args[{i}]";
            var varName = p.Name;
            var isAnyType = p.TypeInfo.SpecialType == SpecialType.System_Object;

            if (p.IsOptional)
            {
                sb.AppendLine(
                    $"{indent}        var {varName} = args.Length > {i} ? {GetUnmarshalWithDefault(p.TypeInfo, argName, p.DefaultValue)} : {p.DefaultValue ?? GetDefaultValueForType(p.TypeInfo)};");
            }
            else
            {
                sb.AppendLine($"{indent}        if ({argName}.IsNullOrUndefined())");
                sb.AppendLine(
                    $"{indent}            return ctx.ThrowError(global::HakoJS.VM.JSErrorType.Type, \"Parameter '{varName}' cannot be null or undefined\");");
                if (!isAnyType)
                {
                    sb.AppendLine($"{indent}        if (!{GetTypeCheck(p.TypeInfo, argName)})");
                    sb.AppendLine(
                        $"{indent}            return ctx.ThrowError(global::HakoJS.VM.JSErrorType.Type, \"Parameter '{varName}' must be {GetTypeName(p.TypeInfo)}\");");
                }

                sb.AppendLine($"{indent}        var {varName} = {GetStrictUnmarshalCode(p.TypeInfo, argName)};");
            }
        }

        var callArgs = string.Join(", ", delegateInfo.Parameters.Select(p => p.Name));

        if (delegateInfo.IsAsync)
        {
            if (!delegateInfo.IsVoid)
            {
                sb.AppendLine($"{indent}        var result = await {param.Name}({callArgs});");
                sb.AppendLine($"{indent}        return {GetMarshalCode(delegateInfo.ReturnType, "result", "ctx")};");
            }
            else
            {
                sb.AppendLine($"{indent}        await {param.Name}({callArgs});");
                sb.AppendLine($"{indent}        return ctx.Undefined();");
            }
        }
        else
        {
            if (!delegateInfo.IsVoid)
            {
                sb.AppendLine($"{indent}        var result = {param.Name}({callArgs});");
                sb.AppendLine($"{indent}        return {GetMarshalCode(delegateInfo.ReturnType, "result", "ctx")};");
            }
            else
            {
                sb.AppendLine($"{indent}        {param.Name}({callArgs});");
                sb.AppendLine($"{indent}        return ctx.Undefined();");
            }
        }

        sb.AppendLine($"{indent}    }}");
        sb.AppendLine($"{indent}    catch (global::System.Exception ex)");
        sb.AppendLine($"{indent}    {{");
        sb.AppendLine($"{indent}        return ctx.ThrowError(ex);");
        sb.AppendLine($"{indent}    }}");
        sb.AppendLine($"{indent}}});");
        sb.AppendLine($"{indent}obj.SetProperty(\"{param.JsName}\", {param.Name}Func);");

        if (param.TypeInfo is { IsNullable: true, IsValueType: false })
        {
            indent = indent.Substring(0, indent.Length - 4);
            sb.AppendLine($"{indent}}}");
        }
    }

    private static void GenerateObjectFromJSValue(StringBuilder sb, ObjectModel model)
    {
        var newModifier = model.HasJSObjectBase() ? "new " : "";

        sb.AppendLine(
            $"    public static {newModifier}{model.TypeName} FromJSValue(global::HakoJS.VM.Realm realm, global::HakoJS.VM.JSValue jsValue)");
        sb.AppendLine("    {");

        sb.AppendLine("        if (!jsValue.IsObject())");
        sb.AppendLine("            throw new global::System.InvalidOperationException(\"JSValue must be an object\");");
        sb.AppendLine();

        if (model.IsAbstract())
        {
            sb.AppendLine("        if (jsValue.TryReify(out var reified))");
            sb.AppendLine("        {");
            sb.AppendLine($"            if (reified is {model.TypeName} typedInstance)");
            sb.AppendLine("                return typedInstance;");
            sb.AppendLine();
            sb.AppendLine(
                $"            throw new global::System.InvalidOperationException(\"Reified instance is not assignable to {model.TypeName}\");");
            sb.AppendLine("        }");
            sb.AppendLine();
            sb.AppendLine("        throw new global::System.NotSupportedException(");
            sb.AppendLine($"            \"Cannot instantiate abstract type '{model.TypeName}'. \" +");
            sb.AppendLine(
                "            \"The JavaScript object should have a _hako_id indicating a concrete derived type.\");");
            sb.AppendLine("    }");
            return;
        }

        foreach (var param in model.ConstructorParameters)
        {
            if (param.IsDelegate && param.DelegateInfo != null)
                GenerateFunctionToDelegate(sb, param, "        ");
            else
                GenerateObjectPropertyUnmarshaling(sb, param, "        ");

            sb.AppendLine();
        }

        var constructorArgs = string.Join(", ",
            model.ConstructorParameters.Select(p => EscapeIdentifierIfNeeded(ToCamelCase(p.Name))));
        sb.AppendLine($"        var instance = new {model.TypeName}({constructorArgs});");

        sb.AppendLine();
        sb.AppendLine("        return instance;");
        sb.AppendLine("    }");
    }

    private static void GenerateFunctionToDelegate(StringBuilder sb, RecordParameterModel param, string indent)
    {
        var delegateInfo = param.DelegateInfo!;
        var isRequired = !param.IsOptional;
        var propVarName = ToCamelCase(param.Name) + "Prop";
        var localVarName = EscapeIdentifierIfNeeded(ToCamelCase(param.Name));

        sb.AppendLine($"{indent}using var {propVarName} = jsValue.GetProperty(\"{param.JsName}\");");

        if (isRequired)
        {
            sb.AppendLine($"{indent}if ({propVarName}.IsNullOrUndefined())");
            sb.AppendLine(
                $"{indent}    throw new global::System.InvalidOperationException(\"Required property '{param.JsName}' is missing\");");
            sb.AppendLine($"{indent}if (!{propVarName}.IsFunction())");
            sb.AppendLine(
                $"{indent}    throw new global::System.InvalidOperationException(\"Property '{param.JsName}' must be a function\");");
            sb.AppendLine();

            sb.AppendLine($"{indent}var captured{ToPascalCase(param.Name)} = {propVarName}.Dup();");
            sb.AppendLine($"{indent}realm.TrackValue(captured{ToPascalCase(param.Name)});");
            GenerateDelegateWrapper(sb, param, $"{indent}var {localVarName} = ", ";");
        }
        else
        {
            var typeDeclaration = param.TypeInfo.FullName;
            if (!param.TypeInfo.IsValueType && !typeDeclaration.EndsWith("?"))
                typeDeclaration += "?";

            sb.AppendLine($"{indent}{typeDeclaration} {localVarName};");
            sb.AppendLine($"{indent}if ({propVarName}.IsNullOrUndefined())");
            sb.AppendLine($"{indent}{{");
            sb.AppendLine($"{indent}    {localVarName} = {param.DefaultValue ?? "null"};");
            sb.AppendLine($"{indent}}}");
            sb.AppendLine($"{indent}else");
            sb.AppendLine($"{indent}{{");
            sb.AppendLine($"{indent}    if (!{propVarName}.IsFunction())");
            sb.AppendLine(
                $"{indent}        throw new global::System.InvalidOperationException(\"Property '{param.JsName}' must be a function\");");
            sb.AppendLine();
            sb.AppendLine($"{indent}    var captured{ToPascalCase(param.Name)} = {propVarName}.Dup();");
            sb.AppendLine($"{indent}    realm.TrackValue(captured{ToPascalCase(param.Name)});");
            GenerateDelegateWrapper(sb, param, $"{indent}    {localVarName} = ", ";");
            sb.AppendLine($"{indent}}}");
        }
    }

    private static void GenerateDelegateWrapper(StringBuilder sb, RecordParameterModel param, string prefix,
        string suffix)
    {
        var delegateInfo = param.DelegateInfo!;
        var indent = new string(' ', prefix.Length);

        // Build parameter list with proper nullability
        var delegateParams = string.Join(", ", delegateInfo.Parameters.Select(p =>
        {
            var typeName = p.TypeInfo.FullName;
            if (!p.TypeInfo.IsValueType && p.TypeInfo.IsNullable && !typeName.EndsWith("?"))
                typeName += "?";
            return $"{typeName} {p.Name}";
        }));

        // Check if this is a named delegate type (not Func/Action)
        var isNamedDelegate = !param.TypeInfo.FullName.Contains("System.Func") &&
                              !param.TypeInfo.FullName.Contains("System.Action");

        string delegateType;
        if (isNamedDelegate)
        {
            // Use the actual delegate type name
            delegateType = param.TypeInfo.FullName;
        }
        else
        {
            // Generate Func/Action type
            var paramTypes = string.Join(", ", delegateInfo.Parameters.Select(p =>
            {
                var typeName = p.TypeInfo.FullName;
                if (!p.TypeInfo.IsValueType && p.TypeInfo.IsNullable && !typeName.EndsWith("?"))
                    typeName += "?";
                return typeName;
            }));

            if (delegateInfo.IsVoid)
            {
                delegateType = paramTypes.Length > 0
                    ? $"global::System.Action<{paramTypes}>"
                    : "global::System.Action";
            }
            else
            {
                var returnTypeStr = delegateInfo.IsAsync
                    ? $"global::System.Threading.Tasks.Task<{delegateInfo.ReturnType.FullName}>"
                    : delegateInfo.ReturnType.FullName;

                delegateType = paramTypes.Length > 0
                    ? $"global::System.Func<{paramTypes}, {returnTypeStr}>"
                    : $"global::System.Func<{returnTypeStr}>";
            }
        }

        if (delegateInfo.IsAsync)
        {
            sb.Append($"{prefix}new {delegateType}(async ({delegateParams}) =>");
            sb.AppendLine();
            sb.AppendLine($"{indent}{{");

            var invokeArgs = delegateInfo.Parameters.Any()
                ? string.Join(", ", delegateInfo.Parameters.Select(p => p.Name))
                : "";

            if (!delegateInfo.IsVoid)
            {
                if (invokeArgs.Length > 0)
                    sb.AppendLine(
                        $"{indent}    using var result = await captured{ToPascalCase(param.Name)}!.InvokeAsync({invokeArgs});");
                else
                    sb.AppendLine(
                        $"{indent}    using var result = await captured{ToPascalCase(param.Name)}!.InvokeAsync();");

                sb.AppendLine($"{indent}    return {GetUnmarshalCode(delegateInfo.ReturnType, "result")};");
            }
            else
            {
                if (invokeArgs.Length > 0)
                    sb.AppendLine($"{indent}    await captured{ToPascalCase(param.Name)}!.InvokeAsync({invokeArgs});");
                else
                    sb.AppendLine($"{indent}    await captured{ToPascalCase(param.Name)}!.InvokeAsync();");
            }

            sb.Append($"{indent}}})");
            sb.AppendLine(suffix);
        }
        else
        {
            sb.Append($"{prefix}new {delegateType}(({delegateParams}) =>");
            sb.AppendLine();
            sb.AppendLine($"{indent}{{");

            var invokeArgs = delegateInfo.Parameters.Any()
                ? string.Join(", ", delegateInfo.Parameters.Select(p => p.Name))
                : "";

            if (!delegateInfo.IsVoid)
            {
                if (invokeArgs.Length > 0)
                    sb.AppendLine(
                        $"{indent}    using var result = captured{ToPascalCase(param.Name)}!.Invoke({invokeArgs});");
                else
                    sb.AppendLine($"{indent}    using var result = captured{ToPascalCase(param.Name)}!.Invoke();");

                sb.AppendLine($"{indent}    return {GetUnmarshalCode(delegateInfo.ReturnType, "result")};");
            }
            else
            {
                if (invokeArgs.Length > 0)
                    sb.AppendLine($"{indent}    captured{ToPascalCase(param.Name)}!.Invoke({invokeArgs});");
                else
                    sb.AppendLine($"{indent}    captured{ToPascalCase(param.Name)}!.Invoke();");
            }

            sb.Append($"{indent}}})");
            sb.AppendLine(suffix);
        }
    }

    private static void GenerateObjectPropertyUnmarshaling(StringBuilder sb, RecordParameterModel param, string indent)
    {
        var isRequired = !param.IsOptional;
        var propVarName = ToCamelCase(param.Name) + "Prop";
        var localVarName = EscapeIdentifierIfNeeded(ToCamelCase(param.Name));
        var isAnyType = param.TypeInfo.SpecialType == SpecialType.System_Object;

        sb.AppendLine($"{indent}using var {propVarName} = jsValue.GetProperty(\"{param.JsName}\");");

        if (isRequired)
        {
            if (param.TypeInfo.IsNullable)
            {
                var typeDeclaration = param.TypeInfo.FullName;
                if (!param.TypeInfo.IsValueType && !typeDeclaration.EndsWith("?"))
                    typeDeclaration += "?";

                if (!isAnyType)
                {
                    sb.AppendLine(
                        $"{indent}if (!{propVarName}.IsNullOrUndefined() && !{GetTypeCheck(param.TypeInfo, propVarName)})");
                    sb.AppendLine(
                        $"{indent}    throw new global::System.InvalidOperationException(\"Property '{param.Name}' must be {GetTypeName(param.TypeInfo)}\");");
                }

                sb.AppendLine(
                    $"{indent}var {localVarName} = {propVarName}.IsNullOrUndefined() ? null : {GetStrictUnmarshalCode(param.TypeInfo, propVarName, "realm")};");
            }
            else
            {
                sb.AppendLine($"{indent}if ({propVarName}.IsNullOrUndefined())");
                sb.AppendLine(
                    $"{indent}    throw new global::System.InvalidOperationException(\"Property '{param.Name}' cannot be null or undefined\");");
                if (!isAnyType)
                {
                    sb.AppendLine($"{indent}if (!{GetTypeCheck(param.TypeInfo, propVarName)})");
                    sb.AppendLine(
                        $"{indent}    throw new global::System.InvalidOperationException(\"Property '{param.Name}' must be {GetTypeName(param.TypeInfo)}\");");
                }

                sb.AppendLine(
                    $"{indent}var {localVarName} = {GetStrictUnmarshalCode(param.TypeInfo, propVarName, "realm")};");
            }
        }
        else
        {
            var typeDeclaration = param.TypeInfo.FullName;

            if (!param.TypeInfo.IsValueType && !typeDeclaration.EndsWith("?"))
                typeDeclaration += "?";

            sb.AppendLine($"{indent}{typeDeclaration} {localVarName};");
            sb.AppendLine($"{indent}if ({propVarName}.IsNullOrUndefined())");
            sb.AppendLine($"{indent}{{");
            sb.AppendLine(
                $"{indent}    {localVarName} = {param.DefaultValue ?? GetDefaultValueForType(param.TypeInfo)};");
            sb.AppendLine($"{indent}}}");
            sb.AppendLine($"{indent}else");
            sb.AppendLine($"{indent}{{");
            if (!isAnyType)
            {
                sb.AppendLine($"{indent}    if (!{GetTypeCheck(param.TypeInfo, propVarName)})");
                sb.AppendLine(
                    $"{indent}        throw new global::System.InvalidOperationException(\"Property '{param.Name}' must be {GetTypeName(param.TypeInfo)}\");");
            }

            sb.AppendLine(
                $"{indent}    {localVarName} = {GetStrictUnmarshalCode(param.TypeInfo, propVarName, "realm")};");
            sb.AppendLine($"{indent}}}");
        }
    }

    #endregion

    #region Parameter Unmarshaling

    private static void GenerateParameterUnmarshaling(StringBuilder sb, ParameterModel param, int index, string indent)
    {
        var argName = $"args[{index}]";
        var isRequired = !param.IsOptional;
        var type = param.TypeInfo;
        var paramName = EscapeIdentifierIfNeeded(param.Name);
        var isAnyType = type.SpecialType == SpecialType.System_Object;

        if (param.IsDelegate && param.DelegateInfo != null)
        {
            var typeDeclaration = type.FullName;
            if (!type.IsValueType && !isRequired && !typeDeclaration.EndsWith("?"))
                typeDeclaration += "?";

            if (isRequired)
            {
                sb.AppendLine($"{indent}if ({argName}.IsNullOrUndefined())");
                sb.AppendLine(
                    $"{indent}    return ctx.ThrowError(global::HakoJS.VM.JSErrorType.Type, \"Parameter '{param.Name}' cannot be null or undefined\");");
                sb.AppendLine($"{indent}if (!{argName}.IsFunction())");
                sb.AppendLine(
                    $"{indent}    return ctx.ThrowError(global::HakoJS.VM.JSErrorType.Type, \"Parameter '{param.Name}' must be a function\");");
                sb.AppendLine();

                GenerateDelegateParameterWrapper(sb, param, argName, paramName, indent, false);
            }
            else
            {
                sb.AppendLine($"{indent}{typeDeclaration} {paramName};");
                sb.AppendLine($"{indent}if (args.Length > {index} && !{argName}.IsNullOrUndefined())");
                sb.AppendLine($"{indent}{{");
                sb.AppendLine($"{indent}    if (!{argName}.IsFunction())");
                sb.AppendLine(
                    $"{indent}        return ctx.ThrowError(global::HakoJS.VM.JSErrorType.Type, \"Parameter '{param.Name}' must be a function\");");
                sb.AppendLine();

                GenerateDelegateParameterWrapper(sb, param, argName, paramName, indent + "    ", true);

                sb.AppendLine($"{indent}}}");
                sb.AppendLine($"{indent}else");
                sb.AppendLine($"{indent}{{");
                sb.AppendLine($"{indent}    {paramName} = {param.DefaultValue ?? "null"};");
                sb.AppendLine($"{indent}}}");
            }

            return;
        }

        if (type.UnderlyingType != null)
        {
            var underlyingTypeInfo = CreateTypeInfo(type.UnderlyingType);
            var isUnderlyingAnyType = underlyingTypeInfo.SpecialType == SpecialType.System_Object;

            if (isRequired)
            {
                sb.AppendLine($"{indent}var arg{index}IsNull = {argName}.IsNullOrUndefined();");
                if (!isUnderlyingAnyType)
                {
                    sb.AppendLine($"{indent}if (!arg{index}IsNull && !{GetTypeCheck(underlyingTypeInfo, argName)})");
                    sb.AppendLine(
                        $"{indent}    return ctx.ThrowError(global::HakoJS.VM.JSErrorType.Type, \"Parameter '{param.Name}' must be {GetTypeName(underlyingTypeInfo)}\");");
                }

                sb.AppendLine(
                    $"{indent}var {paramName} = arg{index}IsNull ? null : ({type.FullName})({GetStrictUnmarshalCode(underlyingTypeInfo, argName)});");
            }
            else
            {
                var defaultExpr = param.DefaultValue ?? "null";
                sb.AppendLine($"{indent}{type.FullName} {paramName};");
                sb.AppendLine($"{indent}if (args.Length > {index})");
                sb.AppendLine($"{indent}{{");
                sb.AppendLine($"{indent}    var arg{index}IsNull = {argName}.IsNullOrUndefined();");
                if (!isUnderlyingAnyType)
                {
                    sb.AppendLine(
                        $"{indent}    if (!arg{index}IsNull && !{GetTypeCheck(underlyingTypeInfo, argName)})");
                    sb.AppendLine(
                        $"{indent}        return ctx.ThrowError(global::HakoJS.VM.JSErrorType.Type, \"Parameter '{param.Name}' must be {GetTypeName(underlyingTypeInfo)}\");");
                }

                sb.AppendLine(
                    $"{indent}    {paramName} = arg{index}IsNull ? null : ({type.FullName})({GetStrictUnmarshalCode(underlyingTypeInfo, argName)});");
                sb.AppendLine($"{indent}}}");
                sb.AppendLine($"{indent}else");
                sb.AppendLine($"{indent}{{");
                sb.AppendLine($"{indent}    {paramName} = {defaultExpr};");
                sb.AppendLine($"{indent}}}");
            }
        }
        else if (isRequired)
        {
            if (type.IsNullable)
            {
                sb.AppendLine($"{indent}var arg{index}IsNull = {argName}.IsNullOrUndefined();");
                if (!isAnyType)
                {
                    sb.AppendLine($"{indent}if (!arg{index}IsNull && !{GetTypeCheck(type, argName)})");
                    sb.AppendLine(
                        $"{indent}    return ctx.ThrowError(global::HakoJS.VM.JSErrorType.Type, \"Parameter '{param.Name}' must be {GetTypeName(type)}\");");
                }

                sb.AppendLine(
                    $"{indent}var {paramName} = arg{index}IsNull ? null : {GetStrictUnmarshalCode(type, argName)};");
            }
            else
            {
                sb.AppendLine($"{indent}if ({argName}.IsNullOrUndefined())");
                sb.AppendLine(
                    $"{indent}    return ctx.ThrowError(global::HakoJS.VM.JSErrorType.Type, \"Parameter '{param.Name}' cannot be null or undefined\");");
                if (!isAnyType)
                {
                    sb.AppendLine($"{indent}if (!{GetTypeCheck(type, argName)})");
                    sb.AppendLine(
                        $"{indent}    return ctx.ThrowError(global::HakoJS.VM.JSErrorType.Type, \"Parameter '{param.Name}' must be {GetTypeName(type)}\");");
                }

                sb.AppendLine($"{indent}var {paramName} = {GetStrictUnmarshalCode(type, argName)};");
            }
        }
        else
        {
            var defaultExpr = param.DefaultValue ?? GetDefaultValueForType(type);

            var typeDeclaration = type.FullName;
            if (!type.IsValueType && !typeDeclaration.EndsWith("?"))
                typeDeclaration += "?";

            if (type.IsNullable)
            {
                sb.AppendLine($"{indent}{typeDeclaration} {paramName};");
                sb.AppendLine($"{indent}if (args.Length > {index})");
                sb.AppendLine($"{indent}{{");
                sb.AppendLine($"{indent}    var arg{index}IsNull = {argName}.IsNullOrUndefined();");
                if (!isAnyType)
                {
                    sb.AppendLine($"{indent}    if (!arg{index}IsNull && !{GetTypeCheck(type, argName)})");
                    sb.AppendLine(
                        $"{indent}        return ctx.ThrowError(global::HakoJS.VM.JSErrorType.Type, \"Parameter '{param.Name}' must be {GetTypeName(type)}\");");
                }

                sb.AppendLine(
                    $"{indent}    {paramName} = arg{index}IsNull ? null : {GetStrictUnmarshalCode(type, argName)};");
                sb.AppendLine($"{indent}}}");
                sb.AppendLine($"{indent}else");
                sb.AppendLine($"{indent}{{");
                sb.AppendLine($"{indent}    {paramName} = {defaultExpr};");
                sb.AppendLine($"{indent}}}");
            }
            else
            {
                sb.AppendLine($"{indent}{typeDeclaration} {paramName};");
                sb.AppendLine($"{indent}if (args.Length > {index})");
                sb.AppendLine($"{indent}{{");
                sb.AppendLine($"{indent}    if ({argName}.IsNullOrUndefined())");
                sb.AppendLine(
                    $"{indent}        return ctx.ThrowError(global::HakoJS.VM.JSErrorType.Type, \"Parameter '{param.Name}' cannot be null or undefined\");");
                if (!isAnyType)
                {
                    sb.AppendLine($"{indent}    if (!{GetTypeCheck(type, argName)})");
                    sb.AppendLine(
                        $"{indent}        return ctx.ThrowError(global::HakoJS.VM.JSErrorType.Type, \"Parameter '{param.Name}' must be {GetTypeName(type)}\");");
                }

                sb.AppendLine($"{indent}    {paramName} = {GetStrictUnmarshalCode(type, argName)};");
                sb.AppendLine($"{indent}}}");
                sb.AppendLine($"{indent}else");
                sb.AppendLine($"{indent}{{");
                sb.AppendLine($"{indent}    {paramName} = {defaultExpr};");
                sb.AppendLine($"{indent}}}");
            }
        }
    }

    private static void GenerateDelegateParameterWrapper(StringBuilder sb, ParameterModel param, string jsValueName,
        string paramName, string indent, bool isOptionalAssignment)
    {
        var delegateInfo = param.DelegateInfo!;

        // Build parameter list with proper nullability
        var delegateParams = string.Join(", ", delegateInfo.Parameters.Select(p =>
        {
            var typeName = p.TypeInfo.FullName;
            // Add nullable marker for reference types if needed
            if (!p.TypeInfo.IsValueType && p.TypeInfo.IsNullable && !typeName.EndsWith("?"))
                typeName += "?";
            return $"{typeName} {p.Name}";
        }));

        // Check if this is a named delegate type (not Func/Action)
        var isNamedDelegate = !param.TypeInfo.FullName.Contains("System.Func") &&
                              !param.TypeInfo.FullName.Contains("System.Action");

        string delegateType;
        if (isNamedDelegate)
        {
            // Use the actual delegate type name
            delegateType = param.TypeInfo.FullName;
        }
        else
        {
            // Generate Func/Action type
            var paramTypes = string.Join(", ", delegateInfo.Parameters.Select(p =>
            {
                var typeName = p.TypeInfo.FullName;
                if (!p.TypeInfo.IsValueType && p.TypeInfo.IsNullable && !typeName.EndsWith("?"))
                    typeName += "?";
                return typeName;
            }));

            if (delegateInfo.IsVoid)
            {
                delegateType = paramTypes.Length > 0
                    ? $"global::System.Action<{paramTypes}>"
                    : "global::System.Action";
            }
            else
            {
                var returnTypeStr = delegateInfo.IsAsync
                    ? $"global::System.Threading.Tasks.Task<{delegateInfo.ReturnType.FullName}>"
                    : delegateInfo.ReturnType.FullName;

                delegateType = paramTypes.Length > 0
                    ? $"global::System.Func<{paramTypes}, {returnTypeStr}>"
                    : $"global::System.Func<{returnTypeStr}>";
            }
        }

        var assignmentPrefix = isOptionalAssignment ? $"{indent}{paramName} = " : $"{indent}var {paramName} = ";

        if (delegateInfo.IsAsync)
        {
            sb.AppendLine($"{assignmentPrefix}new {delegateType}(async ({delegateParams}) =>");
            sb.AppendLine($"{indent}{{");

            // Build arguments array for Invoke
            var invokeArgs = delegateInfo.Parameters.Any()
                ? string.Join(", ", delegateInfo.Parameters.Select(p => p.Name))
                : "";

            if (!delegateInfo.IsVoid)
            {
                if (invokeArgs.Length > 0)
                    sb.AppendLine($"{indent}    using var result = await {jsValueName}.InvokeAsync({invokeArgs});");
                else
                    sb.AppendLine($"{indent}    using var result = await {jsValueName}.InvokeAsync();");

                sb.AppendLine($"{indent}    return {GetUnmarshalCode(delegateInfo.ReturnType, "result")};");
            }
            else
            {
                if (invokeArgs.Length > 0)
                    sb.AppendLine($"{indent}    await {jsValueName}.InvokeAsync({invokeArgs});");
                else
                    sb.AppendLine($"{indent}    await {jsValueName}.InvokeAsync();");
            }

            sb.AppendLine($"{indent}}});");
        }
        else
        {
            sb.AppendLine($"{assignmentPrefix}new {delegateType}(({delegateParams}) =>");
            sb.AppendLine($"{indent}{{");

            // Build arguments array for Invoke
            var invokeArgs = delegateInfo.Parameters.Any()
                ? string.Join(", ", delegateInfo.Parameters.Select(p => p.Name))
                : "";

            if (!delegateInfo.IsVoid)
            {
                if (invokeArgs.Length > 0)
                    sb.AppendLine($"{indent}    using var result = {jsValueName}.Invoke({invokeArgs});");
                else
                    sb.AppendLine($"{indent}    using var result = {jsValueName}.Invoke();");

                sb.AppendLine($"{indent}    return {GetUnmarshalCode(delegateInfo.ReturnType, "result")};");
            }
            else
            {
                if (invokeArgs.Length > 0)
                    sb.AppendLine($"{indent}    {jsValueName}.Invoke({invokeArgs});");
                else
                    sb.AppendLine($"{indent}    {jsValueName}.Invoke();");
            }

            sb.AppendLine($"{indent}}});");
        }
    }

    private static void GenerateValueUnmarshaling(StringBuilder sb, TypeInfo type, string varName, string jsValueName,
        string indent)
    {
        var isAnyType = type.SpecialType == SpecialType.System_Object;

        if (type.UnderlyingType != null)
        {
            var underlyingTypeInfo = CreateTypeInfo(type.UnderlyingType);
            var isUnderlyingAnyType = underlyingTypeInfo.SpecialType == SpecialType.System_Object;

            if (!isUnderlyingAnyType)
            {
                sb.AppendLine(
                    $"{indent}if (!{jsValueName}.IsNullOrUndefined() && !{GetTypeCheck(underlyingTypeInfo, jsValueName)})");
                sb.AppendLine(
                    $"{indent}    return ctx.ThrowError(global::HakoJS.VM.JSErrorType.Type, \"Value '{varName}' must be {GetTypeName(underlyingTypeInfo)}\");");
            }

            sb.AppendLine(
                $"{indent}var {varName} = {jsValueName}.IsNullOrUndefined() ? null : ({type.FullName})({GetStrictUnmarshalCode(underlyingTypeInfo, jsValueName)});");
        }
        else if (type.IsNullable)
        {
            if (!isAnyType)
            {
                sb.AppendLine($"{indent}if (!{jsValueName}.IsNullOrUndefined() && !{GetTypeCheck(type, jsValueName)})");
                sb.AppendLine(
                    $"{indent}    return ctx.ThrowError(global::HakoJS.VM.JSErrorType.Type, \"Value '{varName}' must be {GetTypeName(type)}\");");
            }

            sb.AppendLine(
                $"{indent}var {varName} = {jsValueName}.IsNullOrUndefined() ? null : {GetStrictUnmarshalCode(type, jsValueName)};");
        }
        else
        {
            sb.AppendLine($"{indent}if ({jsValueName}.IsNullOrUndefined())");
            sb.AppendLine(
                $"{indent}    return ctx.ThrowError(global::HakoJS.VM.JSErrorType.Type, \"Value '{varName}' cannot be null or undefined\");");
            if (!isAnyType)
            {
                sb.AppendLine($"{indent}if (!{GetTypeCheck(type, jsValueName)})");
                sb.AppendLine(
                    $"{indent}    return ctx.ThrowError(global::HakoJS.VM.JSErrorType.Type, \"Value '{varName}' must be {GetTypeName(type)}\");");
            }

            sb.AppendLine($"{indent}var {varName} = {GetStrictUnmarshalCode(type, jsValueName)};");
        }
    }

    #endregion

    #region Marshaling Helpers

    private static string GetTypeCheck(TypeInfo type, string jsValueName)
    {
        // For nullable value types, check the underlying type
        if (type.UnderlyingType != null)
        {
            var underlyingTypeInfo = CreateTypeInfo(type.UnderlyingType);
            return GetTypeCheck(underlyingTypeInfo, jsValueName);
        }

        switch (type.SpecialType)
        {
            case SpecialType.System_String:
                return $"{jsValueName}.IsString()";
            case SpecialType.System_Boolean:
                return $"{jsValueName}.IsBoolean()";
            case SpecialType.System_Char:
                return $"{jsValueName}.IsString()";
            case SpecialType.System_Int32:
            case SpecialType.System_Int64:
            case SpecialType.System_Int16:
            case SpecialType.System_Byte:
            case SpecialType.System_SByte:
            case SpecialType.System_UInt32:
            case SpecialType.System_UInt64:
            case SpecialType.System_UInt16:
            case SpecialType.System_Double:
            case SpecialType.System_Single:
                return $"{jsValueName}.IsNumber()";
            case SpecialType.System_DateTime:
                return $"{jsValueName}.IsDate()";
        }

        // Handle [JSEnum]
        if (type.IsEnum)
            return type.IsFlags
                ? $"{jsValueName}.IsNumber()"
                : $"{jsValueName}.IsString()";

        if (type.FullName == "global::System.Byte[]")
            return $"({jsValueName}.IsArrayBuffer() || {jsValueName}.IsTypedArray())";

        if (type.IsArray)
            return $"{jsValueName}.IsArray()";

        return $"{jsValueName}.IsObject()";
    }

    private static string GetStrictUnmarshalCode(TypeInfo type, string jsValueName, string contextVarName = "ctx")
    {
        // Handle nullable value types by unmarshaling the underlying type and casting
        if (type.UnderlyingType != null)
        {
            var underlyingTypeInfo = CreateTypeInfo(type.UnderlyingType);
            var underlyingCode = GetStrictUnmarshalCode(underlyingTypeInfo, jsValueName, contextVarName);
            return $"({type.FullName})({underlyingCode})";
        }

        if (type.SpecialType is SpecialType.System_Object)
            return $"{jsValueName}.GetNativeValue<object>()";

        if (type is { IsGenericDictionary: true, KeyTypeSymbol: not null, ValueTypeSymbol: not null })
        {
            var keyTypeInfo = CreateTypeInfo(type.KeyTypeSymbol);
            var isKeyValid = keyTypeInfo.SpecialType == SpecialType.System_String ||
                             IsNumericType(type.KeyTypeSymbol);

            if (isKeyValid)
            {
                var isValueMarshalable = ImplementsIJSMarshalable(type.ValueTypeSymbol) ||
                                         HasAttribute(type.ValueTypeSymbol,
                                             "HakoJS.SourceGeneration.JSClassAttribute") ||
                                         HasAttribute(type.ValueTypeSymbol,
                                             "HakoJS.SourceGeneration.JSObjectAttribute");

                if (isValueMarshalable)
                    return $"{jsValueName}.ToDictionaryOf<{type.KeyType}, {type.ValueType}>()";

                return $"{jsValueName}.ToDictionary<{type.KeyType}, {type.ValueType}>()";
            }

            return $"{jsValueName}.GetNativeValue<object>()";
        }

        if (type is { IsGenericCollection: true, ItemTypeSymbol: not null })
        {
            var isItemMarshalable = ImplementsIJSMarshalable(type.ItemTypeSymbol) ||
                                    HasAttribute(type.ItemTypeSymbol, "HakoJS.SourceGeneration.JSClassAttribute") ||
                                    HasAttribute(type.ItemTypeSymbol, "HakoJS.SourceGeneration.JSObjectAttribute");

            var arrayMethod = isItemMarshalable ? "ToArrayOf" : "ToArray";
            var arrayExpr = $"{jsValueName}.{arrayMethod}<{type.ItemType}>()";

            // Check the specific collection type and convert appropriately
            var typeDefinition = type.FullName.Replace("global::", "");
            if (typeDefinition.StartsWith("System.Collections.Generic.List<"))
                // For List<T>, wrap the array in a List constructor
                return $"new {type.FullName}({arrayExpr})";

            if (typeDefinition.StartsWith("System.Collections.Generic.IList<") ||
                typeDefinition.StartsWith("System.Collections.Generic.ICollection<") ||
                typeDefinition.StartsWith("System.Collections.Generic.IEnumerable<") ||
                typeDefinition.StartsWith("System.Collections.Generic.IReadOnlyList<") ||
                typeDefinition.StartsWith("System.Collections.Generic.IReadOnlyCollection<"))
                // For interfaces, the array can be used directly (implicit conversion)
                return arrayExpr;

            // Fallback for other collection types
            return arrayExpr;
        }

        switch (type.SpecialType)
        {
            case SpecialType.System_String:
                return $"{jsValueName}.AsString()";
            case SpecialType.System_Char:
                return $"{jsValueName}.AsString().FirstOrDefault()";
            case SpecialType.System_Int32:
                return $"(int){jsValueName}.AsNumber()";
            case SpecialType.System_Int64:
                return $"(long){jsValueName}.AsNumber()";
            case SpecialType.System_Double:
                return $"{jsValueName}.AsNumber()";
            case SpecialType.System_Single:
                return $"(float){jsValueName}.AsNumber()";
            case SpecialType.System_Boolean:
                return $"{jsValueName}.AsBoolean()";
            case SpecialType.System_Int16:
                return $"(short){jsValueName}.AsNumber()";
            case SpecialType.System_Byte:
                return $"(byte){jsValueName}.AsNumber()";
            case SpecialType.System_SByte:
                return $"(sbyte){jsValueName}.AsNumber()";
            case SpecialType.System_UInt32:
                return $"(uint){jsValueName}.AsNumber()";
            case SpecialType.System_UInt64:
                return $"(ulong){jsValueName}.AsNumber()";
            case SpecialType.System_UInt16:
                return $"(ushort){jsValueName}.AsNumber()";

            case SpecialType.System_DateTime:
                return $"{jsValueName}.AsDateTime()";
        }

        if (type.IsEnum)
        {
            if (type.IsFlags) return $"({type.FullName})(int){jsValueName}.AsNumber()";

            return $"global::System.Enum.Parse<{type.FullName}>({jsValueName}.AsString(), ignoreCase: true)";
        }

        if (type.FullName == "global::System.Byte[]")
            return
                $"({jsValueName}.IsArrayBuffer() ? {jsValueName}.CopyArrayBuffer() : {jsValueName}.CopyTypedArray())";

        if (type is { IsArray: true, ElementType: not null })
        {
            var elementTypeName = type.ElementType.Replace("global::", "");

            // Check if element is a [JSEnum]
            if (type.ItemTypeSymbol != null && type.ItemTypeSymbol.IsJSEnum())
            {
                var fullEnumType = type.ItemTypeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);

                if (type.ItemTypeSymbol.IsJSEnumFlags())
                {
                    return $"{jsValueName}.ToArray<int>().Select(x => ({fullEnumType})x).ToArray()";
                }
                else
                {
                    return
                        $"{jsValueName}.ToArray<string>().Select(x => global::System.Enum.Parse<{fullEnumType}>(x, ignoreCase: true)).ToArray()";
                }
            }

            if (IsPrimitiveTypeName(type.ElementType) ||
                elementTypeName is "System.Object" or "object")
                return $"{jsValueName}.ToArray<{type.ElementType}>()";

            return $"{jsValueName}.ToArrayOf<{type.ElementType}>()";
        }

        return $"{type.FullName}.FromJSValue({contextVarName}, {jsValueName})";
    }

    private static string GetUnmarshalCode(TypeInfo type, string jsValueName, string contextVarName = "realm")
    {
        if (type.SpecialType == SpecialType.System_Object)
            return $"{jsValueName}.GetNativeValue<object>()";

        switch (type.SpecialType)
        {
            case SpecialType.System_String:
                return $"{jsValueName}.AsString()";
            case SpecialType.System_Int32:
                return $"(int){jsValueName}.AsNumber()";
            case SpecialType.System_Int64:
                return $"(long){jsValueName}.AsNumber()";
            case SpecialType.System_Double:
                return $"{jsValueName}.AsNumber()";
            case SpecialType.System_Single:
                return $"(float){jsValueName}.AsNumber()";
            case SpecialType.System_Boolean:
                return $"{jsValueName}.AsBoolean()";
            case SpecialType.System_Int16:
                return $"(short){jsValueName}.AsNumber()";
            case SpecialType.System_Byte:
                return $"(byte){jsValueName}.AsNumber()";
            case SpecialType.System_SByte:
                return $"(sbyte){jsValueName}.AsNumber()";
            case SpecialType.System_UInt32:
                return $"(uint){jsValueName}.AsNumber()";
            case SpecialType.System_UInt64:
                return $"(ulong){jsValueName}.AsNumber()";
            case SpecialType.System_UInt16:
                return $"(ushort){jsValueName}.AsNumber()";
            case SpecialType.System_DateTime:
                return $"{jsValueName}.AsDateTime()";
        }

        if (type.FullName == "global::System.Byte[]")
            return
                $"({jsValueName}.IsArrayBuffer() ? {jsValueName}.CopyArrayBuffer() : {jsValueName}.CopyTypedArray())";

        if (type is { IsArray: true, ElementType: not null })
        {
            // Check if element is a [JSEnum]
            if (type.ItemTypeSymbol != null && type.ItemTypeSymbol.IsJSEnum())
            {
                var fullEnumType = type.ItemTypeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);

                if (type.ItemTypeSymbol.IsJSEnumFlags())
                {
                    return $"{jsValueName}.ToArray<int>().Select(x => ({fullEnumType})x).ToArray()";
                }
                else
                {
                    return
                        $"{jsValueName}.ToArray<string>().Select(x => global::System.Enum.Parse<{fullEnumType}>(x, ignoreCase: true)).ToArray()";
                }
            }
        }

        return $"{type.FullName}.FromJSValue({contextVarName}, {jsValueName})";
    }

    private static string GetUnmarshalWithDefault(TypeInfo type, string jsValueName, string? defaultValue)
    {
        var unmarshalCode = GetStrictUnmarshalCode(type, jsValueName);
        var defaultExpr = defaultValue ?? GetDefaultValueForType(type);

        if (type.IsNullable) return $"{jsValueName}.IsNullOrUndefined() ? {defaultExpr} : {unmarshalCode}";

        return
            $"!{jsValueName}.IsNullOrUndefined() && {GetTypeCheck(type, jsValueName)} ? {unmarshalCode} : {defaultExpr}";
    }

    private static string GetTypeName(TypeInfo type)
    {
        // For nullable value types, get the name of the underlying type
        if (type.UnderlyingType != null)
        {
            var underlyingTypeInfo = CreateTypeInfo(type.UnderlyingType);
            return GetTypeName(underlyingTypeInfo);
        }

        switch (type.SpecialType)
        {
            case SpecialType.System_String:
                return "a string";
            case SpecialType.System_Char:
                return "a string (single character)";
            case SpecialType.System_Boolean:
                return "a boolean";
            case SpecialType.System_Int32:
            case SpecialType.System_Int64:
            case SpecialType.System_Int16:
            case SpecialType.System_Byte:
            case SpecialType.System_SByte:
            case SpecialType.System_UInt32:
            case SpecialType.System_UInt64:
            case SpecialType.System_UInt16:
            case SpecialType.System_Double:
            case SpecialType.System_Single:
                return "a number";
            case SpecialType.System_DateTime:
                return "a Date";
        }

        // Handle [JSEnum]
        if (type.IsEnum) return type.IsFlags ? "a number (flags enum)" : "a string (enum)";

        if (type.FullName == "global::System.Byte[]")
            return "an ArrayBuffer or TypedArray";

        if (type.IsArray)
            return "an array";

        return "an object";
    }

    private static string GetMarshalCode(TypeInfo type, string valueName, string ctxName)
    {
        if (type.UnderlyingType != null)
        {
            var underlyingTypeInfo = CreateTypeInfo(type.UnderlyingType);
            var underlyingMarshalCode = GetMarshalCodeForPrimitive(underlyingTypeInfo, valueName, ctxName);
            return
                $"({valueName}.HasValue ? {underlyingMarshalCode.Replace(valueName, $"{valueName}.Value")} : {ctxName}.Null())";
        }

        return GetMarshalCodeForPrimitive(type, valueName, ctxName);
    }

    private static string GetMarshalCodeForPrimitive(TypeInfo type, string valueName, string ctxName)
    {
        if (type.SpecialType is SpecialType.System_Object)
            return $"{ctxName}.NewValue({valueName})";

        if (type.IsGenericDictionary && type.KeyTypeSymbol != null && type.ValueTypeSymbol != null)
        {
            var isKeyValid = type.KeyTypeSymbol.SpecialType == SpecialType.System_String ||
                             IsNumericType(type.KeyTypeSymbol);

            if (isKeyValid)
            {
                var isValueMarshalable = ImplementsIJSMarshalable(type.ValueTypeSymbol) ||
                                         HasAttribute(type.ValueTypeSymbol,
                                             "HakoJS.SourceGeneration.JSClassAttribute") ||
                                         HasAttribute(type.ValueTypeSymbol,
                                             "HakoJS.SourceGeneration.JSObjectAttribute");

                if (type.IsNullable && !type.IsValueType)
                {
                    if (isValueMarshalable)
                        return
                            $"({valueName} == null ? {ctxName}.Null() : {valueName}.ToJSDictionaryOf<{type.KeyType}, {type.ValueType}>({ctxName}))";

                    return
                        $"({valueName} == null ? {ctxName}.Null() : {valueName}.ToJSDictionary<{type.KeyType}, {type.ValueType}>({ctxName}))";
                }

                if (isValueMarshalable)
                    return $"{valueName}.ToJSDictionaryOf<{type.KeyType}, {type.ValueType}>({ctxName})";

                return $"{valueName}.ToJSDictionary<{type.KeyType}, {type.ValueType}>({ctxName})";
            }

            return $"{ctxName}.NewValue({valueName})";
        }

        if (type.IsGenericCollection && type.ItemTypeSymbol != null)
        {
            var isItemMarshalable = ImplementsIJSMarshalable(type.ItemTypeSymbol) ||
                                    HasAttribute(type.ItemTypeSymbol, "HakoJS.SourceGeneration.JSClassAttribute") ||
                                    HasAttribute(type.ItemTypeSymbol, "HakoJS.SourceGeneration.JSObjectAttribute");

            if (type.IsNullable && !type.IsValueType)
            {
                if (isItemMarshalable)
                    return
                        $"({valueName} == null ? {ctxName}.Null() : {valueName}.ToJSArrayOf<{type.ItemType}>({ctxName}))";

                return
                    $"({valueName} == null ? {ctxName}.Null() : {valueName}.ToJSArray<{type.ItemType}>({ctxName}))";
            }

            if (isItemMarshalable) return $"{valueName}.ToJSArrayOf<{type.ItemType}>({ctxName})";

            return $"{valueName}.ToJSArray<{type.ItemType}>({ctxName})";
        }

        switch (type.SpecialType)
        {
            case SpecialType.System_String:
                return type.IsNullable
                    ? $"({valueName} == null ? {ctxName}.Null() : {ctxName}.NewString({valueName}))"
                    : $"{ctxName}.NewString({valueName})";
            case SpecialType.System_Char:
                return $"{ctxName}.NewString({valueName}.ToString())";
            case SpecialType.System_Boolean:
                return $"({valueName} ? {ctxName}.True() : {ctxName}.False())";
            case SpecialType.System_Int32:
            case SpecialType.System_Int64:
            case SpecialType.System_Int16:
            case SpecialType.System_Byte:
            case SpecialType.System_SByte:
            case SpecialType.System_UInt32:
            case SpecialType.System_UInt64:
            case SpecialType.System_UInt16:
                return $"{ctxName}.NewNumber({valueName})";
            case SpecialType.System_Double:
            case SpecialType.System_Single:
                return
                    $"{ctxName}.NewNumber(double.IsNaN({valueName}) || double.IsInfinity({valueName}) ? 0.0 : {valueName})";
            case SpecialType.System_Void:
                return $"{ctxName}.Undefined()";

            case SpecialType.System_DateTime:
                return type.IsNullable
                    ? $"({valueName}.HasValue ? {ctxName}.NewDate({valueName}.Value) : {ctxName}.Null())"
                    : $"{ctxName}.NewDate({valueName})";
        }

        if (type.IsEnum)
        {
            if (type.IsFlags)
                return type.IsNullable
                    ? $"({valueName} == null ? {ctxName}.Null() : {ctxName}.NewNumber((int){valueName}))"
                    : $"{ctxName}.NewNumber((int){valueName})";

            return type.IsNullable
                ? $"({valueName} == null ? {ctxName}.Null() : {ctxName}.NewString({valueName}.ToStringFast()))"
                : $"{ctxName}.NewString({valueName}.ToStringFast())";
        }

        if (type.FullName == "global::System.Byte[]")
            return type.IsNullable
                ? $"({valueName} == null ? {ctxName}.Null() : {ctxName}.NewArrayBuffer({valueName}))"
                : $"{ctxName}.NewArrayBuffer({valueName})";

        if (type is { IsArray: true, ElementType: not null })
        {
            var elementTypeName = type.ElementType.Replace("global::", "");

            // Check if element is a [JSEnum]
            if (type.ItemTypeSymbol != null && type.ItemTypeSymbol.IsJSEnum())
            {
                if (type.ItemTypeSymbol.IsJSEnumFlags())
                {
                    return type.IsNullable
                        ? $"({valueName} == null ? {ctxName}.Null() : {ctxName}.NewArray({valueName}.Select(x => (int)x)))"
                        : $"{ctxName}.NewArray({valueName}.Select(x => (int)x))";
                }
                else
                {
                    return type.IsNullable
                        ? $"({valueName} == null ? {ctxName}.Null() : {ctxName}.NewArray({valueName}.Select(x => x.ToStringFast())))"
                        : $"{ctxName}.NewArray({valueName}.Select(x => x.ToStringFast()))";
                }
            }

            if (IsPrimitiveTypeName(type.ElementType) ||
                elementTypeName is "System.Object" or "object")
                return type.IsNullable
                    ? $"({valueName} == null ? {ctxName}.Null() : {valueName}.ToJSArray({ctxName}))"
                    : $"{valueName}.ToJSArray({ctxName})";

            return type.IsNullable
                ? $"({valueName} == null ? {ctxName}.Null() : {valueName}.ToJSArrayOf({ctxName}))"
                : $"{valueName}.ToJSArrayOf({ctxName})";
        }

        if (!type.IsValueType || type.IsNullable)
            return $"({valueName} == null ? {ctxName}.Null() : {valueName}.ToJSValue({ctxName}))";

        return $"{valueName}.ToJSValue({ctxName})";
    }

    private static string GetDefaultValueForType(TypeInfo type)
    {
        if (type.UnderlyingType != null)
            return "null";

        if (type is { IsNullable: true, IsValueType: false })
            return "null";

        return type.SpecialType switch
        {
            SpecialType.System_String => "string.Empty",
            SpecialType.System_Boolean => "false",
            SpecialType.System_Char => "'\\0'",
            SpecialType.System_Single => "0.0f",
            SpecialType.System_Double => "0.0",
            SpecialType.System_Decimal => "0.0m",
            SpecialType.System_Int32 or SpecialType.System_Int64 or SpecialType.System_Int16 or
                SpecialType.System_Byte or SpecialType.System_SByte or SpecialType.System_UInt32 or
                SpecialType.System_UInt64 or SpecialType.System_UInt16 => "0",
            _ => $"default({type.FullName})"
        };
    }

    #endregion
}